/**
 * MyMin - JSMin and packer like alternative parser for both JavaScript and CSS
 *
 * This class is a jsmin alternative, based on same parser logic but modified
 * to mantain performances and to parse correctly JavaScript conditional comments too.
 * This file contains another class too, called MyMinCompressor, based on
 * personal fast and strongly compatible decompression method, inspired by
 * Dean Edwards packer logic ( http://dean.edwards.name/packer/ )
 * but compatible with every kind of source code.
 *
 * CLIENT SIDE
 * Internet Explorer <= 5 requires a correct String.prototype.replace
 * implementation (http://www.3site.eu/replace.js) to decode compressed source.
 * This code is compatible with every JavaScript compatible browser.
 *
 * SERVER SIDE
 * .NET 2.0 or greater is required.
 * The best practice to use this code is caching results without run-time
 * evaluation (your server should be stressed too much with big files)
 *
 * Permission is hereby granted to use this version of the library under the
 * same terms as jsmin.php, which has the following license:
 *
 * --
 * Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * The Software shall be used for Good, not Evil.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * --
 *
 * @class	MyMin
 *		MyMinCompressor
 *		MyMinException
 * @author	Andrea Giammarchi <http://www.3site.eu>
 * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
 * @copyright 2007 Ryan Grove <ryan@wonko.com> (PHP port)
 * @copyright 2007 Andrea Giammarchi (improvements + MyMinCompressor + MyMinCSS)
 * @license http://opensource.org/licenses/mit-license.php MIT License
 * @version 1.0.1 (2007-10-05)
 */

namespace PeterD.util
{
	class MyMin
	{

		#region public constants
		public const System.Char LF = '\n', SPACE = ' ', EOS = System.Char.MinValue;
		#endregion

		#region protected variables
		protected System.Boolean cc_on;
		protected System.Char a, ahead, b;
		protected System.Int32 index = 0, length;
		protected System.String input;
		protected System.Text.StringBuilder output = new System.Text.StringBuilder();
		#endregion

		#region static public methods
		static public System.String parse(System.String input)
		{
			return MyMin.parse(input, true);
		}
		static public System.String parse(System.String input, System.Boolean cc_on)
		{
			return new MyMin(input, cc_on).ToString();
		}
		static public System.String parse(System.String input, System.Boolean cc_on, System.Boolean css)
		{
			return css ? new MyMinCSS(input, cc_on).ToString() : new MyMin(input, cc_on).ToString();
		}
		#endregion

		#region constructor
		public MyMin(System.String input)
		{
			this.init(input, true);
		}

		public MyMin(System.String input, System.Boolean cc_on)
		{
			this.init(input, cc_on);
		}
		#endregion

		#region public override methods
		public override System.String ToString()
		{
			return this.output.ToString().TrimStart().Replace("\n\n", "\n");
		}

		public static implicit operator System.String(MyMin o)
		{
			return o.ToString();
		}
		#endregion

		#region virtual protected methods
		virtual protected void action(System.Int32 i)
		{
			if (i < 2)
				this.output.Append(this.a);
			if (i < 3)
			{
				this.a = this.b;
				if (this.a == '\'' || this.a == '"')
				{
					while (true)
					{
						this.output.Append(this.a);
						if (!this.nextCharNoSlash(this.b, "Unterminated string literal."))
							break;
					}
				}
			}
			if (i < 4)
			{
				this.b = this.next();
				if (this.b == '/')
				{
					switch (this.a)
					{
						case MyMin.LF:
						case MyMin.SPACE:
						case '{':
						case ';':

						case '(':
						case ',':
						case '=':
						case ':':
						case '[':
						case '!':
						case '&':
						case '|':
						case '?':
							if ((MyMin.LF == this.a || MyMin.SPACE == this.a) && !this.spaceBeforeRegExp(this.output.ToString()))
								break;
							this.output.Append(this.a);
							this.output.Append(this.b);
							while (this.nextCharNoSlash('/', "Unterminated regular expression literal."))
								this.output.Append(this.a);
							this.b = this.next();
							break;
					}
				}
			}
		}

		virtual protected void appendComment(System.Int32 pos, System.String open, System.String close)
		{
			this.output.Append(this.a);
			this.output.Append(open);
			this.output.Append(new MyMin(this.input.Substring(this.index, pos - this.index), this.cc_on));
			this.output.Append(close);
			this.index = pos;
			this.a = MyMin.LF;
		}

		virtual protected void conditionalComment(System.Char find)
		{
			System.Int32 pos = this.input.IndexOf(find, this.index);
			if (pos < 0)
				pos = this.length;
			this.appendComment(pos, "//", find.ToString());
		}

		virtual protected void conditionalComment(System.String find)
		{
			System.Int32 pos = this.input.IndexOf(find, this.index);
			if (pos < 0)
				throw new MyMinException("Unterminated comment.");
			this.appendComment(pos, "/*", find);
		}

		virtual protected System.Char get()
		{
			System.Char c = this.ahead;
			this.ahead = MyMin.EOS;
			if (c == MyMin.EOS && this.index < this.length)
				c = this.input[this.index++];
			return (c == MyMin.EOS || c == MyMin.LF || c >= MyMin.SPACE) ? c : MyMin.SPACE;
		}

		virtual protected void init(System.String input, System.Boolean cc_on)
		{
			this.input = System.Text.RegularExpressions.Regex.Replace(input.Trim(), "(\n\r|\r\n|\r|\n)+", MyMin.LF.ToString());
			this.length = this.input.Length;
			this.cc_on = cc_on;
			this.a = MyMin.LF;
			this.action(3);
			while (this.a != MyMin.EOS)
			{
				switch (this.a)
				{
					case MyMin.SPACE:
						this.action(this.isAlNum(this.b) ? 1 : 2);
						break;
					case MyMin.LF:
						switch (this.b)
						{
							case '{':
							case '[':
							case '(':
							case '+':
							case '-':
								this.action(1);
								break;
							case MyMin.SPACE:
								this.action(3);
								break;
							default:
								this.action(this.isAlNum(this.b) ? 1 : 2);
								break;
						}
						break;
					default:
						switch (this.b)
						{
							case MyMin.SPACE:
								this.action(this.isAlNum(this.a) ? 1 : 3);
								break;
							case MyMin.LF:
								switch (this.a)
								{
									case '}':
									case ']':
									case ')':
									case '+':
									case '-':
									case '"':
									case '\'':
										this.action(1);
										break;
									default:
										this.action(this.isAlNum(this.a) ? 1 : 3);
										break;
								}
								break;
							default:
								this.action(1);
								break;
						}
						break;
				}
			}
		}

		virtual protected System.Boolean isAlNum(System.Char c)
		{
			return c > 126 || c == '\\' || System.Text.RegularExpressions.Regex.IsMatch(c.ToString(), "^(\\w|\\$)$");
		}

		virtual protected System.Char next()
		{
			System.Char c = this.get();
			System.Boolean loop = true;
			if (c == '/')
			{
				switch (this.ahead = this.get())
				{
					case '/':
						if (this.cc_on && this.input[this.index] == '@')
							this.conditionalComment(MyMin.LF);
						while (loop)
						{
							c = this.get();
							if (c <= MyMin.LF)
								loop = false;
						}
						break;
					case '*':
						this.get();
						if (this.cc_on && this.input[this.index] == '@')
							this.conditionalComment("*/");
						while (loop)
						{
							switch (this.get())
							{
								case '*':
									if ((this.ahead = this.get()) == '/')
									{
										this.get();
										c = MyMin.SPACE;
										loop = false;
									}
									break;
								case MyMin.EOS:
									throw new MyMinException("Unterminated comment.");
							}
						}
						break;
				}
			}
			return c;
		}

		virtual protected System.Boolean nextCharNoSlash(System.Char c, System.String message)
		{
			System.Boolean loop = true;
			this.a = this.get();
			if (this.a == c)
				loop = false;
			else
			{
				if (this.a == '\\')
				{
					this.output.Append(this.a);
					this.a = this.get();
				}
				if (this.a <= MyMin.LF)
					throw new MyMinException(message);
			}
			return loop;
		}

		virtual protected System.Boolean spaceBeforeRegExp(System.String output)
		{
			System.Int32 i, length = output.Length;
			System.Boolean result = false;
			System.String tmp;
			System.String[] reserved = "case.else.in.return.typeof".Split('.');
			for (i = 0; i < 5 && !result; i++)
			{
				if (length == reserved[i].Length)
					result = reserved[i] == output;
				else if (length > reserved[i].Length)
				{
					tmp = output.Substring(length - reserved[i].Length - 1);
					result = tmp.Substring(1) == reserved[i] && !this.isAlNum(tmp[0]);
				}
			}
			return length < 2 ? true : result;
		}
		#endregion
	}

	class MyMinCompressor
	{

		#region protected variables
		protected System.Int32 baseLength = 0;
		protected System.String baseString = "0123456789abcdefghijklmnopqrstuvwxyz";
		protected System.Collections.Generic.Dictionary<System.String, System.Int32> dict;
		protected System.Collections.Generic.Dictionary<System.String, System.String>[] list;
		protected System.String[] keywords;
		#endregion

		#region constructor
		public MyMinCompressor()
		{
			this.init(36);
		}

		public MyMinCompressor(System.Int32 baseLength)
		{
			this.init(baseLength);
		}
		#endregion

		#region public methods
		public System.String getCSSMachine(System.String css)
		{
			return this.getCSSMachine(css, System.String.Empty);
		}

		public System.String getCSSMachine(System.String css, System.String media)
		{
			System.Text.StringBuilder result = new System.Text.StringBuilder();
			result.Append("(function(M,y,m,i,n,C,S,s){if(m[n]){S=m.styleSheets;s=m.createElement(\"style\");s.type=\"text/css\";s.media=C||\"all\";M=M.replace(/\\w+/g,function(m){return y[parseInt(m,");
			result.Append(this.baseLength);
			result.Append(")]});m[n](\"head\")[0][i](s);/*@cc_on if(!(S[S.length-1].cssText=M))@*/s[i](m.createTextNode(M));m.write('<link hide=\"')}})('");
			result.Append(this.parse(css));
			result.Append("','");
			result.Append(System.String.Join(".", this.keywords));
			result.Append("'.split('.'),document,'appendChild','getElementsByTagName'");
			result.Append(media != System.String.Empty ? ",'" + media + "'" : "");
			result.Append(')');
			return result.ToString();
		}

		public System.String getJSMachine(System.String js)
		{
			System.Text.StringBuilder result = new System.Text.StringBuilder();
			result.Append("eval((function(M){return '");
			result.Append(this.parse(js));
			result.Append("'.replace(/\\w+/g,function(m){return M[parseInt(m,");
			result.Append(this.baseLength);
			result.Append(")]})})('");
			result.Append(System.String.Join(".", this.keywords));
			result.Append("'.split('.')))");
			return result.ToString();
		}

		public System.String getMachine(System.String js, System.String css)
		{
			return this.getMachine(js, css, System.String.Empty);
		}

		public System.String getMachine(System.String js, System.String css, System.String media)
		{
			System.Text.StringBuilder result = new System.Text.StringBuilder();
			System.String[] source;
			result.Append(js);
			result.Append(MyMin.EOS);
			result.Append(css);
			source = this.parse(result.ToString()).Split(MyMin.EOS);
			result = new System.Text.StringBuilder();
			result.Append("eval((function(M,y,m,i,n,C,S,s){if(m[n]){S=m.styleSheets;s=m.createElement(\"style\");s.type=\"text/css\";s.media=C||\"all\";C=M('");
			result.Append(source[1]);
			result.Append("',y);m[n](\"head\")[0][i](s);/*@cc_on if(!(S[S.length-1].cssText=C))@*/s[i](m.createTextNode(C));m.write('<link hide=\"')}return s?M('");
			result.Append(source[0]);
			result.Append("',y):\"\"})(function(s,l){return s.replace(/\\w+/g,function(m){return l[parseInt(m,");
			result.Append(this.baseLength);
			result.Append(")]})},'");
			result.Append(System.String.Join(".", this.keywords));
			result.Append("'.split('.'),document,'appendChild','getElementsByTagName'");
			result.Append(media != System.String.Empty ? ",'" + media + "'" : "");
			result.Append("))");
			return result.ToString();
		}
		#endregion

		#region virtual protected methods
		virtual protected System.String convertToBase(System.Int32 num)
		{
			System.Int32 module = 0;
			System.String result = "";
			while (num > 0)
			{
				result = this.baseString[(module = num % this.baseLength)].ToString() + result;
				num = (System.Int32)((num - module) / this.baseLength);
			}
			return result != "" ? result : this.baseString[0].ToString();
		}

		virtual protected System.String countMatches(System.Text.RegularExpressions.Match m)
		{
			if (!this.dict.ContainsKey(m.Value))
				this.dict.Add(m.Value, 0);
			this.dict[m.Value]++;
			return System.String.Empty;
		}

		virtual protected void init(System.Int32 baseLength)
		{
			this.baseLength = baseLength;
			this.baseString = this.baseString.Substring(0, baseLength);
		}

		virtual protected System.String parse(System.String source)
		{
			System.Int32 i = 0;
			System.Collections.Generic.Dictionary<System.String, System.String> tmp;
			this.dict = new System.Collections.Generic.Dictionary<System.String, System.Int32>();
			source = source.Replace("\\", "\\\\").Replace("'", "\\'");
			new System.Text.RegularExpressions.Regex(@"\w+").Replace(source, new System.Text.RegularExpressions.MatchEvaluator(this.countMatches));
			this.list = new System.Collections.Generic.Dictionary<System.String, System.String>[this.dict.Keys.Count];
			foreach (System.String key in this.dict.Keys)
			{
				tmp = new System.Collections.Generic.Dictionary<System.String, System.String>();
				tmp.Add("match", key);
				tmp.Add("count", this.dict[key].ToString());
				this.list[i++] = tmp;
			}
			this.sort();
			this.keywords = new System.String[i = this.list.Length];
			while (i-- > 0)
			{
				tmp = this.list[i];
				this.dict[this.keywords[i] = tmp["match"]] = i;
			}
			source = new System.Text.RegularExpressions.Regex(@"\w+").Replace(source, new System.Text.RegularExpressions.MatchEvaluator(this.replaceMatches));
			source = new System.Text.RegularExpressions.Regex("(\r\n|\n\r|\n|\r)+").Replace(source, "\\n");
			return source;
		}

		virtual protected System.String replaceMatches(System.Text.RegularExpressions.Match m)
		{
			return this.convertToBase(this.dict[m.Value]);
		}

		virtual protected void sort()
		{
			System.Boolean sorted = false;
			System.Collections.Generic.Dictionary<System.String, System.String> tmp;
			for (System.Int32 i = 1, j = this.list.Length; i < j; i++)
			{
				if (this.sortKeywords(this.list[i - 1], this.list[i]))
				{
					sorted = true;
					tmp = this.list[i];
					this.list[i] = this.list[i - 1];
					this.list[i - 1] = tmp;
					i = i > 2 ? i - 2 : 0;
				}
			}
			if (sorted)
				this.sort();
		}

		virtual protected System.Boolean sortKeywords(
			System.Collections.Generic.Dictionary<System.String, System.String> a,
			System.Collections.Generic.Dictionary<System.String, System.String> b
		)
		{
			System.Int32 ac = System.Convert.ToInt32(a["count"]), bc = System.Convert.ToInt32(b["count"]);
			return (ac < bc) || (ac == bc && a["match"].Length < b["match"].Length);
		}
		#endregion
	}

	class MyMinCSS
	{

		#region protected variables
		protected System.String output;
		#endregion

		#region constructor
		public MyMinCSS(System.String input)
		{
			this.output = this.init(input, true);
		}

		public MyMinCSS(System.String input, System.Boolean cc_on)
		{
			this.output = this.init(input, cc_on);
		}
		#endregion

		#region public override methods
		public override System.String ToString()
		{
			return this.output.ToString().TrimEnd();
		}

		public static implicit operator System.String(MyMinCSS o)
		{
			return o.ToString();
		}
		#endregion

		#region virtual protected methods
		virtual protected System.Boolean action(System.Char c)
		{
			System.Boolean r = true;
			switch (c)
			{
				case '}':
				case '{':
				case ';':
				case ',':
				case ':':
					r = false;
					break;
			}
			return r;
		}

		virtual protected System.String init(System.String input, System.Boolean cc_on)
		{
			System.Char c;
			System.Text.StringBuilder output = new System.Text.StringBuilder();
			input = System.Text.RegularExpressions.Regex.Replace(input.Trim(), "(\\s)+", MyMin.SPACE.ToString());
			for (System.Int32 i = 0, l = input.Length; i < l; i++)
			{
				c = input[i];
				switch (c)
				{
					case MyMin.SPACE:
						if (++i < l)
						{
							if (this.action(input[i]))
							{
								if (-1 < (i - 2))
								{
									if (this.action(input[i - 2]))
										output.Append(c);
								}
								else
									output.Append(c);
							};
							--i;
						};
						break;
					case '/':
						if (++i < l)
						{
							c = input[i];
							if (c == '*')
							{
								if (++i < l)
								{
									l = input.IndexOf("*/", i);
									if (-1 < l)
									{
										i = l + 2;
										l = input.Length;
										if (i < l)
										{
											--i;
											input = System.String.Format("{0};{1}", input.Substring(0, i), input.Substring(i + 1));
										}
									}
									else
										throw new MyMinException("Unterminated comment.");
								}
								else
									throw new MyMinException("Unterminated comment.");
							}
							else
								output.Append(input[--i]);
						};
						break;
					default:
						output.Append(c);
						break;
				}
			}
			return output.ToString().TrimEnd();
		}
		#endregion

	}

	class MyMinException : System.Exception
	{
		#region constructor
		public MyMinException(System.String message) : base(message) { }
		#endregion
	}
}